﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Ns
{
    /// <summary>
    /// Represents a recurrence whose execution depends on its descendants. 
    /// This is an abstract class.
    /// </summary>
    [Serializable]
    [DataContract]
    [KnownType(typeof(PeriodRecurrence))]
    [KnownType(typeof(DailyRecurrence))]
    [KnownType(typeof(WeeklyRecurrence))]
    [KnownType(typeof(MonthlyRecurrence))]
    public abstract class Recurrence : ICloneable
    {
        #region Methods

        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns>A copy of this instance.</returns>
        public abstract object Clone();

        /// <summary>
        /// Schedules the specified number of <paramref name="occurrences"/> from <see cref="DateTime.Now"/>.
        /// </summary>
        /// <param name="occurrences">The number of occurrences to schedule. Specify zero (0) to schedule indefinitely.</param>
        /// <returns>A sequence of <see cref="OccurrenceSchedule"/>.</returns>
        public IEnumerable<OccurrenceSchedule> Schedule(int occurrences)
        {
            return Schedule(occurrences, DateTime.Now); 
        }

        /// <summary>
        /// Schedules the specified number of <paramref name="occurrences"/> from <paramref name="now"/>.
        /// </summary>
        /// <param name="occurrences">The number of occurrences to schedule. Specify zero (0) to schedule indefinitely.</param>
        /// <param name="now">The current date and time assumed to be an universal time if not specified to avoid ambiguous dates.</param>
        /// <returns>A sequence of <see cref="OccurrenceSchedule"/>.</returns>
        public IEnumerable<OccurrenceSchedule> Schedule(int occurrences, DateTime now)
        {
            var occurrence = 0;
            while (occurrences == 0 || occurrence < occurrences)
            {
                var nextSchedule = GetNextSchedule(now);
                yield return nextSchedule;

                now = nextSchedule.OccursOn;
                if (occurrences != -1)
                    occurrence += 1;
            }
        }

        /// <summary>
        /// Gets the schedule of the next occurrence from <see cref="DateTime.Now"/>.
        /// </summary>
        /// <returns>The schedule of the next occurrence.</returns>
        public OccurrenceSchedule GetNextSchedule()
        {
            return GetNextSchedule(DateTime.Now);
        }

        /// <summary>
        /// Gets the schedule of the next occurrence from <paramref name="now"/>.
        /// </summary>
        /// <param name="now">The current date and time assumed to be an universal time if not specified to avoid ambiguous dates.</param>
        /// <returns>The schedule of the next occurrence.</returns>
        public virtual OccurrenceSchedule GetNextSchedule(DateTime now)
        {
            var occursOn = GetNextDateTime(now.AsUniversalTime()).ToUniversalTime().ToLocalTime();
            var dueTime = occursOn.SubtractWithDaylightTime(now);
            return new OccurrenceSchedule(occursOn, dueTime);
        }

        /// <summary>
        /// Gets the date and time of the next occurrence from <paramref name="now"/>.
        /// </summary>
        /// <param name="now">The current date and time assumed to be an universal time if not specified to avoid ambiguous dates.</param>
        /// <returns>The date and time of the next occurrence in local time.</returns>
        protected abstract DateTime GetNextDateTime(DateTime now);

        #endregion

        #region Constructors
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Recurrence"/> class.
        /// </summary>
        protected Recurrence()
        {
        }

        #endregion
    }
}